#!/usr/bin/php5
<?php

/**
 * class Hoodog
 *
 * The main class for the hoodog flooding protector
 *
 * @package Hoodog
 * @subpackage Hoodog
 * @version 1.1
 * @author hoohead, ixiter
 * @since Version 1.1
 *
 */
class Hoodog {

    /**
     * $mypath
     *
     * Path to this file
     *
     * @author ixiter
     * @since Version 1.1
     * @var string
     */
    private $mypath;
    /**
     * $default_settings
     *
     * Default settings for hoodog
     *
     * @author ixiter
     * @since Version 1.1
     * @var array
     */
    private $default_settings = array(
        // general settings
        'general' => array(
            'maxcalls' => '180',
            'shellcommand' => "netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr",
        ),
        // protect settings
        'protect' => array(
            'ping' => 'true',
            'hitcount' => '15',
            'useblacklist' => 'true',
        ),
        // email settings
        'email' => array(
            'to' => 'peter.liebetrau@gmail.com',
            'subject' => 'Flooder geblockt',
            'message' => 'Flooder mit der IP: %s erfolgreich nach %d calls geblockt',
            'from' => '<hoodog> hoodog@ixiter.com',
        ),
        // logfile settings
        'logfile' => array(
            'path' => '/var/www/logs/hoodoglog.html',
        ),
    );
    /**
     * $settings
     *
     * Used settings for hoodog, read from hoodog.ini
     *
     * @author ixiter
     * @since Version 1.1
     * @var array
     */
    private $settings = array( );
    /**
     * $blacklist
     *
     * The blacklisted hosts and IPs, read from blacklist.ini
     *
     * @author ixiter
     * @since Version 1.1
     * @var array
     */
    private $blacklist = array( );
    /**
     * $whitelist
     *
     * The whitelisted hosts, read from whitelist.ini
     *
     * @author ixiter
     * @since Version 1.1
     * @var string
     */
    private $whitelist = array( );

    /**
     * __construct()
     * 
     * The constructor for hoodog.
     *  <ul>
     *      <li>Read settings from hoodog.ini</li>
     *      <li>read the blacklist from blacklist.ini</li>
     *      <li>read the whitelist from whitelist.ini</li>
     *      <li>check if we run in shell</li>
     *      <li>parses args and switches to related functions</li>
     *      <li>if we are not in shell</li>
     *      <li>show website</li>
     *  </ul>
     * @author ixiter
     * @since Version 1.1
     */
    public function __construct() {
        // checkin
        $this->mypath = realpath( dirname( __FILE__ ) );
        $this->settings = $this->default_settings;
        $this->settings = array_merge( $this->settings, parse_ini_file( $this->mypath . '/hoodog.ini', $process_sections = true ) );
        $this->blacklist = parse_ini_file( $this->mypath . '/blacklist.ini', $process_sections = true );
        $this->whitelist = parse_ini_file( $this->mypath . '/whitelist.ini', $process_sections = true );

        if ( isset( $_SERVER[ 'argv' ] ) ) { // We are called from shell or cronjob
            switch ( true ) { // Wir switchen immer!
                case in_array( '-f', $_Server[ 'argv' ] ): // Damit wir so schön casen können
                    $this->flush_iptables();
                    break;
                default:
                    $this->check_ip();
                    break;
            }
        }
    }

    /**
     * is_true()
     *
     * Defines various values as true and compares the given value to this values.<br>
     * If any value match, it returns true.<br>
     * If no match found, it returns false.
     * 
     * @author ixiter
     * @since Version 1.1
     * @param mixed $value
     * @return bool
     */
    private function is_true( $value ) {
        $positives = array(
            'true',
            '1',
            '-1',
            'yes',
            'on',
            'ja',
            'an',
        );

        return in_array( strtolower( ( string ) $value ), $positives );
    }

    /**
     * is_false()
     *
     * Defines various values as false and compares the given value to this values.<br>
     * If any value match, it returns true.<br>
     * If no match found, it returns false.
     *
     * @author ixiter
     * @since Version 1.1
     * @param mixed $value
     * @return bool
     */
    private function is_false( $value ) {
        $negatives = array(
            'false',
            '0',
            'no',
            'off',
            'nein',
            'aus',
            '',
        );

        return in_array( strtolower( ( string ) $value ), $negatives );
    }

    /**
     * check_ip()
     *
     * The default function when this script is executed in a shell.<br>
     * It checks if the calling host is not whitlisted.<br>
     * If so, it checks if the calling ip has to many calls and blocks it for future calls via iptable.<br>
     * In this case, a notification email is send to the admin.<br>
     * When the host is whitelisted, nothing happens.
     * 
     * @author hoohead, ixiter
     * @since Version 1.1
     */
    private function check_ip() {

        $result = shell_exec( $this->settings[ 'general' ][ 'shellcommand' ] );
        preg_match_all( '/(\d+)\s([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})/i', $result, $matches );
        for ( $i = 0; $i < count( $matches[ 0 ] ); $i ++  ) {
            $calls = $matches[ 1 ][ $i ];
            $ip = $matches[ 2 ][ $i ];
            $host = gethostbyaddr( $ip );

            if ( ! $this->is_whitelisted( $host ) ) {
                if ( $calls >= $this->settings[ 'general' ][ 'maxcalls' ] ) {
                    shell_exec( "iptables -A INPUT -s " . $ip . " -j DROP" );

                    mail(
                            $this->settings[ 'email' ][ 'to' ],
                            $this->settings[ 'email' ][ 'subject' ],
                            sprintf( $this->settings[ 'email' ][ 'message' ], $ip, $calls ),
                            'From: ' . $this->settings[ 'email' ][ 'from' ]
                    );

                    $timestamp = time();
                    $date = date( "d.m.Y", $timestamp );
                    $time = date( "H:i", $timestamp );
                    $file = fopen( $this->settings[ 'logfile' ][ 'path' ], "a+" );
                    fwrite( $file, 'IP: ' . $ip . '  Datum: ' . $date . ' - Uhrzeit: ' . $time . ' - count: ' . $calls . ' <a href="http://www.db.ripe.net/whois?searchtext=' . $ip . '">Whois &lt;' . $host . '&gt;</a><br>' . "\n" );
                    fclose( $file );
                }
            }
        }
    }

    /**
     * flush_iptables()
     *
     * Flushes iptables and set up a new depending on the protect settings.<br>
     * Sends a notification email to the admin.<br>
     * This is a replacement for and combination of flushiptables.php, protect.sh and unfugser.sh.
     *
     * @author ixiter
     * @since Version 1.1
     */
    private function flush_iptables() {
        $sh = 'iptables --flush';

        // Ping Pakete droppen
        if ( $this->is_true( $this->settings[ 'protect' ][ 'ping' ] ) ) {
            $sh .= ' && iptables -A INPUT -p icmp --icmp-type 8 -j DROP';
        }

        // Neue Verbindungen Hitcount-Check von $this->settings['protect']['hitcount'] Conns pro Sekunde
        if ( ( int ) $this->settings[ 'protect' ][ 'hitcount' ] > 0 ) {
            $sh .= ' && iptables -I INPUT -m state --state NEW -m recent --set';
            $sh .= ' && iptables -I INPUT -m state --state NEW -m recent --update --seconds 1 --hitcount ' . $this->settings[ 'protect' ][ 'hitcount' ] . ' -j DROP';
        }

        if ( $this->is_true( $this->settings[ 'protect' ][ 'useblacklist' ] ) ) {
            foreach ( $this->blacklist as $black ) {
                $sh .= ' && iptables -A INPUT -s ' . $black[ 'ip' ] . ' -j DROP';
            }
        }
        $sh .= ' 2>&1';
        shell_exec( $sh );

        mail( $this->settings[ 'email' ][ 'to' ], "iptables flush", ("iptables geflushed" ), 'From: ' . $this->settings[ 'email' ][ 'from' ] );
    }

    /**
     * is_whitelisted($host)
     * 
     * returns true, when teh given host is in the whitelist, or false if not.
     * 
     * @author ixiter
     * @since Version 1.1
     * @param string $host
     * @return bool
     */
    private function is_whitelisted( $host ) {
        $result = false;

        $hostitems = explode( '.', $host );
        foreach ( $this->whitelist as $white ) {
            if ( $hostitems[ count( $hostitems ) - 2 ] == $white[ 'host' ] ) { // host.tld ;)
                $result = true;
                break;
            }
        }

        return $result;
    }

}

$hoodog = new Hoodog();

